package com.lx.boot.log;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.ThrowableProxy;
import ch.qos.logback.core.AppenderBase;
import com.lx.annotation.Note;
import com.lx.boot.OS;
import com.lx.boot.es.ElasticLowerClient;
import com.lx.boot.es.EsData;
import com.lx.boot.es.EsInfo;
import com.lx.boot.log.entity.RunLogMessage;
import com.lx.constant.ConfigEunm;
import com.lx.util.LX;
import com.sun.management.OperatingSystemMXBean;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.slf4j.helpers.MessageFormatter;
import org.springframework.boot.CommandLineRunner;

import java.io.*;
import java.lang.management.ManagementFactory;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.stream.Collectors;

//说明:分布式日志上传
/**{ ylx } 2021/1/21 13:51
 * 可以通过 lxlog.enable=false 进行关闭
 *
 **/
@Slf4j
public class LXLogAppender extends AppenderBase<ILoggingEvent> {

    public final static String CURRENT_IP = getIp();

    @Override
    protected void append(ILoggingEvent event) {
        String traceId = MDC.get("requestId");
        if (LX.isNotEmpty(traceId) && ConfigEunm.LX_LOG_ENABLE.getBoolean()){
            EsInfo esInfo = OS.getBeanProperty(EsInfo.class);
            if (!esInfo.effective()){
                return;
            }
            RunLogMessage logMessage = new RunLogMessage();
            logMessage.setServerName(CURRENT_IP);
            logMessage.setAppName(OS.getApplicationName());
            logMessage.setContent(getMessage(event));
            logMessage.setDtTime(event.getTimeStamp());
            logMessage.setTraceId(traceId);
            logMessage.setLogLevel(event.getLevel().toString());
            logMessage.setFirmId(OS.getFirmId());
            if (event.getCallerData().length>0){
                logMessage.setClassName(event.getCallerData()[0].getClassName());
                logMessage.setMethod(event.getCallerData()[0].getMethodName());
                logMessage.setLineNumber(event.getCallerData()[0].getLineNumber());
            }
            offer(new EsData(logMessage,"plume_log_run_"+LX.getDate("yyyyMMdd")));
        }
    }

    private static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(4,8,10, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(5000), new ThreadPoolExecutor.DiscardOldestPolicy());
    private static final LinkedBlockingQueue<EsData> QUEUES = new LinkedBlockingQueue<>();
    @Note("插入信息")
    public void offer(EsData msg){
        threadPoolExecutor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    if (QUEUES.size()>9000){
                        QUEUES.poll();
                    }
                    QUEUES.offer(msg);
                }catch (Exception e){
                }
            }
        });
    }

    static {
        try {
            uploadToEs();
            logGcInfoThread();
        }catch (Exception e){
            log.error("监听失败!",e);
        }
    }
    private static void logGcInfoThread(){
        String name = ManagementFactory.getRuntimeMXBean().getName();
        String pid = name.split("@")[0];
        String command = "jstat -gc " + pid ;
        String traceId = "jvm-"+pid;
        OperatingSystemMXBean osBean = ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class);
        Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(()->{
            if (ConfigEunm.LX_LOG_GC_ENABLE.getBoolean()){
                OS.setLogTraceId(traceId);
                String jstatGCInfo = getJstatGCInfo(command);
                // 获取CPU使用率
                double cpuUsage = osBean.getSystemCpuLoad();
                // 获取可用内存大小
                long freeMemory = osBean.getFreePhysicalMemorySize()/1024/1024;
                log.info(jstatGCInfo+" "+cpuUsage+" "+freeMemory);
            }
        }, 10, 10, TimeUnit.SECONDS);
    }

    private static void uploadToEs() {
        threadPoolExecutor.execute(new Runnable() {
            @Override
            public void run() {
                List<EsData> ls = null;
                while (true){
                    try {
                        ls = new ArrayList<>(500);
                        for (int j =0; j<500; j++){
                            EsData msg = QUEUES.poll(5,TimeUnit.SECONDS);
                            if (msg == null){
                                break;
                            }
                            ls.add(msg);
                        }
                        if (!ls.isEmpty()){
                            EsInfo esInfo = OS.getBeanProperty(EsInfo.class);
                            Map<String, List<EsData>> listMap = ls.stream().collect(Collectors.groupingBy(EsData::getIndex));
                            for (Map.Entry<String, List<EsData>> listEntry : listMap.entrySet()) {
                                ElasticLowerClient.getInstance(esInfo).insertList(listEntry.getValue(), listEntry.getKey());
                            }
                        }
                    }catch (Exception e){
                        log.error("上传日志失败!",e);
                    }
                }
            }
        });
    }
    private static String getJstatGCInfo(String command) {
        try {
            Process process = Runtime.getRuntime().exec(command);
            // 读取命令的输出
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            reader.readLine();
            String line = reader.readLine();
            // 等待命令执行完成
            process.waitFor();
            // 关闭流和进程
            reader.close();
            process.destroy();
            return line;
        } catch (Exception e) {
            log.error("执行 jstat -gc 命令出错", e);
            return "";
        }
    }

    private static String getMessage(ILoggingEvent logEvent) {
        if (logEvent.getLevel().equals(Level.ERROR)) {
            if (logEvent.getThrowableProxy() != null) {
                ThrowableProxy throwableProxy = (ThrowableProxy) logEvent.getThrowableProxy();
                String[] args = new String[]{erroStackTrace(throwableProxy.getThrowable()).toString()};
                return packageMessage(logEvent.getMessage(), args);
            } else {
                Object[] args = logEvent.getArgumentArray();
                if (args != null) {
                    for (int i = 0; i < args.length; i++) {
                        if (args[i] instanceof Throwable) {
                            args[i] = erroStackTrace(args[i]);
                        }
                    }
                    return packageMessage(logEvent.getMessage(), args);
                }
            }
        }
        return logEvent.getFormattedMessage();
    }
    private static String packageMessage(String message, Object[] args) {
        if (message.indexOf("{}") > 0) {
            return MessageFormatter.arrayFormat(message, args).getMessage();
        }
        StringBuilder builder = new StringBuilder(128);
        builder.append(message);
        for (int i = 0; i < args.length; i++) {
            builder.append("\n").append(args[i]);
        }
        return builder.toString();
    }



    public static String getIp(){
        String localHostAddress = "127.0.0.1";
        try {
            Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();

            while(allNetInterfaces.hasMoreElements()){
                NetworkInterface networkInterface = allNetInterfaces.nextElement();
                Enumeration<InetAddress> address = networkInterface.getInetAddresses();
                while(address.hasMoreElements()){
                    InetAddress inetAddress = address.nextElement();
                    if(inetAddress != null
                            && inetAddress instanceof Inet4Address
                            && !"127.0.0.1".equals(inetAddress.getHostAddress())){
                        localHostAddress = inetAddress.getHostAddress();
                    }
                }
            }
        }catch (Exception e){

        }
        return localHostAddress;
    }

    public static Object erroStackTrace(Object obj) {
        if (obj instanceof Exception) {
            Exception eObj = (Exception) obj;
            StringWriter sw = null;
            PrintWriter pw = null;
            try {
                sw = new StringWriter();
                pw = new PrintWriter(sw);
                String exceptionStack = "\r\n";
                eObj.printStackTrace(pw);
                exceptionStack = sw.toString();
                return exceptionStack;
            } catch (Exception e) {
                e.printStackTrace();
                return obj;
            } finally {
                try {
                    pw.close();
                    sw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } else {
            return obj;
        }
    }


}
